package com.zjw.algorithm.ref;

import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

public class RefCount<T> {

    private final Releaser<T> releaser;
    private final AtomicInteger count;
    private T ref;
    private boolean isRelease;
    private StackTraceElement[] refTrace;
    private StackTraceElement[] releaseTrace;

    public RefCount(T ref) {
        this(ref, null);
    }

    public RefCount(T ref, Releaser<T> releaser) {
        Objects.requireNonNull(ref);
        traceRef();
        this.releaser = releaser;
        this.isRelease = false;
        this.ref = ref;
        this.count = new AtomicInteger(1);
    }

    public T getOrNull() {
        return ref;
    }

    public T get() {
        if (isRelease) {
            throw new RuntimeException("ref is release,last in" + Arrays.toString(releaseTrace));
        }

        return ref;
    }

    public RefCount<T> refOrNull() {
        if (isRelease) {
            return null;
        }
        count.incrementAndGet();
        return this;
    }

    public RefCount<T> ref() {
        if (isRelease) {
            throw new RuntimeException("ref is release");
        }
        count.incrementAndGet();
        traceRef();
        return this;
    }

    public int getRefCount() {
        return count.get();
    }

    public void release() {
        if (isRelease) {
            return;
        }
        int i = count.decrementAndGet();
        if (i <= 0) {
            gc();
        }
        traceRelease();
    }

    public boolean isRelease() {
        return isRelease;
    }

    private void traceRef() {
        refTrace = Thread.currentThread().getStackTrace();
    }

    private void traceRelease() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        releaseTrace = Arrays.copyOfRange(stackTrace, 3, stackTrace.length);
    }

    public void gc() {
        //force gc.
        isRelease = true;
        if (releaser != null) {
            releaser.onRelease(ref);
        }
        ref = null;
    }
}
